package edu.umd.ion.wavemeter.server;

import edu.umd.ion.wavemeter.service.PIDSettings;
import edu.umd.ion.wavemeter.service.PIDSettingsInUse;
import edu.umd.ion.wavemeter.service.WavemeterData;
import edu.umd.ion.wavemeter.service.WavemeterException;
import edu.umd.ion.wavemeter.service.WavemeterSetException;

public class BuiltinPIDControl implements PIDControl {
	private WavemeterData Data;
	private ChannelManager ChannelManager;
	private final int NumVirtChannels = WavemeterConfiguration.NumVirtChannels();
	private final int NumRawChannels = WavemeterConfiguration.NumRawChannels();
	private PIDSettings[] PIDSettingsCache = new PIDSettings[NumRawChannels];
	private long[] LockingTime = new long[NumRawChannels];
	private PIDCourseOracle CourseOracle;
	
	public BuiltinPIDControl(WavemeterData data, ChannelManager manager) {
		Data = data;
		ChannelManager = manager;
		for (int i=0; i<NumRawChannels; ++i) {
			PIDSettingsCache[i] = new PIDSettings(i);
		}
	}

	private class PIDCallback implements RunnableOne<Integer> {
		private PIDSettings PID;
		public PIDCallback(PIDSettings pid) {
			PID = pid;
		}
		public void run(Integer Channel) {
			PIDHardwareSetting Setting = new PIDHardwareSetting();
			if (PID.AutoClearHistory)
				WavemeterLibrary.ClearPIDHistory(Channel+1);
			Setting.Port = Channel+1;
			Setting.PS = WavemeterLibrary.cmiPID_P;
			Setting.dSet = PID.P;
			WavemeterLibrary.SetPIDSetting(Setting);
			//if (result!=0) throw new WavemeterSetException("SetPIDSetting P",result);
			Setting.PS = WavemeterLibrary.cmiPID_I;
			Setting.dSet = PID.I;
			WavemeterLibrary.SetPIDSetting(Setting);
			//if (result!=0) throw new WavemeterSetException("SetPIDSetting I",result);
			Setting.PS = WavemeterLibrary.cmiPID_D;
			Setting.dSet = PID.D;
			WavemeterLibrary.SetPIDSetting(Setting);
			LockingTime[Channel] = System.currentTimeMillis();
		}
	}
	
	public void cleanup() {		
	}
	
	RunnableOne<Integer> Unlock = new RunnableOne<Integer>() {
		@Override
		public void run(Integer result) {
			try {
				unlockPID(result);
			} catch (WavemeterException e) {
			}
		}
		
	};
	
	@Override
	public PIDSettings getPID(int Channel) {
		PIDSettings PID = PIDSettingsCache[Channel];
		PID.Channel = Channel;
		PIDHardwareSetting Setting = new PIDHardwareSetting();
		Setting.Port = PID.Channel+1;
		PID.LockEnabled = PID.LockEnabled && Data.RegulationEnable;
		if (PID.LockEnabled) {
			Setting.PS = WavemeterLibrary.cmiPID_P;
			WavemeterLibrary.GetPIDSetting(Setting);
			PID.P = Setting.dSet;
			Setting.PS = WavemeterLibrary.cmiPID_I;
			WavemeterLibrary.GetPIDSetting(Setting);
			PID.I = Setting.dSet;
			Setting.PS = WavemeterLibrary.cmiPID_D;
			WavemeterLibrary.GetPIDSetting(Setting);
			PID.D = Setting.dSet;
		}
		Setting.PS = WavemeterLibrary.cmiPID_T;
		WavemeterLibrary.GetPIDSetting(Setting);
		PID.Ta = Setting.dSet;
		Setting.PS = WavemeterLibrary.cmiPID_dt;
		WavemeterLibrary.GetPIDSetting(Setting);
		PID.dt = Setting.dSet;
		Setting.PS = WavemeterLibrary.cmiDeviationSensitivityFactor;
		WavemeterLibrary.GetPIDSetting(Setting);
		PID.Sensitivity = Setting.dSet;
		Setting.PS = WavemeterLibrary.cmiDeviationUnit;
		WavemeterLibrary.GetPIDSetting(Setting);
		PID.DeviationUnit = Setting.iSet;
		Setting.PS = WavemeterLibrary.cmiPIDUseT;
		WavemeterLibrary.GetPIDSetting(Setting);
		PID.UseTa = (Setting.iSet!=0);
		Setting.PS = WavemeterLibrary.cmiPIDConstdt;
		WavemeterLibrary.GetPIDSetting(Setting);
		PID.Constantdt = (Setting.iSet!=0);
		Setting.PS = WavemeterLibrary.cmiPID_AutoClearHistory;
		WavemeterLibrary.GetPIDSetting(Setting);
		PID.AutoClearHistory = (Setting.iSet!=0);
		Setting.PS = WavemeterLibrary.cmiDeviationPolarity;
		WavemeterLibrary.GetPIDSetting(Setting);
		PID.Polarity = (Setting.iSet==-1);
		Setting.PS = WavemeterLibrary.cmiDeviationSensitivityDim;
		WavemeterLibrary.GetPIDSetting(Setting);
		PID.SensitivityDimension = Setting.iSet;
		PID.Course = WavemeterLibrary.GetPIDCourseNum(Channel+1);
		Data.ChannelData[Channel].LockEnabled = PID.LockEnabled;
		Data.ChannelData[Channel].PIDCourse = PID.Course;
		return PID;
	}

	@Override
	public PIDSettings setPID(PIDSettings PID) throws WavemeterException {
		PID.LockEnabled = PID.LockEnabled && Data.RegulationEnable;
		PIDSettingsCache[PID.Channel] = PID;
		ChannelManager.setChannelLocked(PID.Channel, PID.LockEnabled);
		PIDHardwareSetting Setting = new PIDHardwareSetting();
		Setting.Port = PID.Channel+1;
		
		if (PID.LockEnabled) {
			ChannelManager.registerRawChannelCallback(PID.Channel, new PIDCallback(PID), 
					Unlock);
		} else {
			unlockPID(PID.Channel);
		}
		//if (result!=0) throw new WavemeterSetException("SetPIDSetting D",result);
		Setting.PS = WavemeterLibrary.cmiPID_T;
		Setting.dSet = PID.Ta;
		WavemeterLibrary.SetPIDSetting(Setting);
		//if (result!=0) throw new WavemeterSetException("SetPIDSetting Ta",result);
		Setting.PS = WavemeterLibrary.cmiPID_dt;
		Setting.dSet = PID.dt;
		WavemeterLibrary.SetPIDSetting(Setting);
		//if (result!=0) throw new WavemeterSetException("SetPIDSetting dt",result);		
		Setting.PS = WavemeterLibrary.cmiDeviationSensitivityFactor;
		Setting.dSet = PID.Sensitivity;
		WavemeterLibrary.SetPIDSetting(Setting);
		//if (result!=0) throw new WavemeterSetException("SetPIDSetting DeviationSensitivityFactor",result);
		Setting.PS = WavemeterLibrary.cmiDeviationUnit;
		Setting.iSet = PID.DeviationUnit;
		WavemeterLibrary.SetPIDSetting(Setting);
		//if (result!=0) throw new WavemeterSetException("SetPIDSetting DeviationUnit",result);
		Setting.PS = WavemeterLibrary.cmiPIDUseT;
		Setting.iSet = PID.UseTa?1:0;
		WavemeterLibrary.SetPIDSetting(Setting);
		//if (result!=0) throw new WavemeterSetException("SetPIDSetting UseTa",result);
		Setting.PS = WavemeterLibrary.cmiPID_AutoClearHistory;
		Setting.iSet = PID.AutoClearHistory?1:0;
		WavemeterLibrary.SetPIDSetting(Setting);
		//if (result!=0) throw new WavemeterSetException("SetPIDSetting AutoClearHistory",result);
		Setting.PS = WavemeterLibrary.cmiPIDConstdt;
		Setting.iSet = PID.Constantdt?1:0;
		WavemeterLibrary.SetPIDSetting(Setting);
		//if (result!=0) throw new WavemeterSetException("SetPIDSetting Constant dt",result);
		Setting.PS = WavemeterLibrary.cmiDeviationPolarity;
		Setting.iSet = PID.Polarity?-1:1;
		WavemeterLibrary.SetPIDSetting(Setting);
		//if (result!=0) throw new WavemeterSetException("SetPIDSetting DeviationPolarity",result);
		Setting.PS = WavemeterLibrary.cmiDeviationSensitivityDim;
		Setting.iSet = PID.SensitivityDimension;
		WavemeterLibrary.SetPIDSetting(Setting);
		//if (result!=0) throw new WavemeterSetException("SetPIDSetting DeviationSensitivityDim",result);
		Data.ChannelData[PID.Channel].LockEnabled = PID.LockEnabled;
		Data.ChannelData[PID.Channel].PIDCourse = PID.Course;
		return PID;
	}

	@Override
	public String setCourse(int Channel, String Course)
			throws WavemeterSetException {
		int result = WavemeterLibrary.SetPIDCourseNum(Channel+1, Course);
		if (result!=0) throw new WavemeterSetException("SetPIDCourse '"+Course+"'",result);
		Course = WavemeterLibrary.GetPIDCourseNum(Channel+1);
		return Course;
	}

	@Override
	public void unlockPID(int channel) throws WavemeterException {
		if (channel>=NumRawChannels) return;
		ChannelManager.setChannelLocked(channel, false);
		PIDHardwareSetting Setting = new PIDHardwareSetting();
		Setting.Port = channel+1;
		Setting.dSet = 0;
		Setting.PS = WavemeterLibrary.cmiPID_P;
		WavemeterLibrary.SetPIDSetting(Setting);
		Setting.PS = WavemeterLibrary.cmiPID_I;
		WavemeterLibrary.SetPIDSetting(Setting);
		Setting.PS = WavemeterLibrary.cmiPID_D;
		WavemeterLibrary.SetPIDSetting(Setting);
		if (Data.ChannelData[channel].LockEnabled) {
			if (CourseOracle!=null) {
				CourseOracle.addCourse(PIDSettingsCache[channel].Course,
						System.currentTimeMillis()-LockingTime[channel], channel);
			}
			Data.ChannelData[channel].LockEnabled = false;
		}
	}

	@Override
	public void clear(int channel) throws WavemeterException {
		int result = WavemeterLibrary.ClearPIDHistory(channel+1);
		if (result!=1) throw new WavemeterException("clearPIDHistory failed");
	}

	@Override
	public void enableRegulation(int channel, boolean enable,
			RunnableTwo<ChannelVoltage, Double> callback) {
		ChannelManager.registerRawChannelCallback(channel, new PIDCallback(PIDSettingsCache[channel]), 
				Unlock);

	}

	public void measuredValue(int channel, long time, double result) {
	}

	@Override
	public void registerCourseOracle(PIDCourseOracle o) {
		CourseOracle = o;
	}

	@Override
	public PIDSettingsInUse getPIDSettingsInUse() {
		PIDSettingsInUse settings = new PIDSettingsInUse();
		settings.IsUsed_Constantdt = settings.IsUsed_dt = true;
		settings.IsUsed_UseTa = settings.IsUsed_Constantdt = true;
		settings.IsUsed_Sensitivity = settings.IsUsed_DeviationUnit = false; 
		settings.IsUsed_SensitivityDimension = false; 
		return settings;
	}

}
